/**
 *              SIMULATION PROJECT
 *
 * Name         Cell Zooming in Green Cellular Networks
 * @File        Traffic.cpp
 * Authors      Bruno Carminati & Marcelo Faleiros
 * Date         March 2013
 *
 * This file is part of the undergraduate final project
 * under the supervision of Prof.Dr. André Noll Barreto
 */
 
#include <math.h>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <list>
#include "Channel.h"
#include "Log.h"
#include "RadioUnit.h"
#include "Traffic.h"

/**
 * Class constructor
 */
Traffic::Traffic(Parameters* parameters, Scheduler* scheduler, Grid* grid, Channel* channel, Log* log, random* r) {
    ptr2param = parameters;
    ptr2sch = scheduler;
    ptr2grid = grid;
    ptr2chan = channel;
    ptr2log = log;
    randGen = r;
    
    /* Schedules first mobile user */
    numberOfCreatedUsers = 0;
    ptr2sch->schedule(Event(timestamp(5.0), (void*)(&wrapperToNewUser), (void*)this));
}

/**
 * Creates a new mobile user and schedules next one
 */
void Traffic::newMobileUser() {
    /* Chooses hotspot and sets mobile user's site */
    int dayHour = (int)(((double)ptr2sch->now()/(60*60))-24*(int)(((double)ptr2sch->now()/(60*60))/24));
    int currDay = (int)((double)ptr2sch->now()/(24*60*60));
    int whichHotspot = chooseHotspot(dayHour,currDay);
    
    double randomXPos = randGen->normal(ptr2grid->hotspotMatrix[whichHotspot][0],ptr2grid->hotspotMatrix[whichHotspot][2]);
    double randomYPos = randGen->normal(ptr2grid->hotspotMatrix[whichHotspot][1],ptr2grid->hotspotMatrix[whichHotspot][2]);
    double randomSpeed = randGen->uniform(0,16);
    int randomDirection = randGen->discrete_uniform(0,7);
    
    /* Creates a new mobile user and links it to a base station */
    MobileUser* mobileUser;
    mobileUser = new MobileUser(randomXPos,randomYPos,1.7);
    mobileUser->speed = randomSpeed;
    mobileUser->direction = randomDirection;
    mobileUser->id = numberOfCreatedUsers;
    mobileUser->rxPower = ptr2chan->link(ptr2grid->baseStationVector,mobileUser);
    mobileUserList.push_back(*mobileUser);
    mobileUser->throughput = ptr2chan->linkThroughput(ptr2grid->baseStationVector,mobileUserList,mobileUser->id);
    
    /* Prints to log file */
    if (ptr2param->logFlag) {
        list<MobileUser>::iterator mobileUserListIterator;
        for(mobileUserListIterator = mobileUserList.begin(); mobileUserListIterator != mobileUserList.end(); mobileUserListIterator++) {
            *(ptr2log->traffic) << mobileUserListIterator->id 
                                << "\t" << mobileUserListIterator->speed 
                                << "\t" << mobileUserListIterator->direction 
                                << "\t" << mobileUserListIterator->xPos 
                                << "\t" << mobileUserListIterator->yPos 
                                << "\t" << mobileUserListIterator->zPos 
                                << "\t" << mobileUserListIterator->whichBaseStation 
                                << "\t" << mobileUserListIterator->throughput 
                                << "\t" << 10*log10(1000*mobileUserListIterator->rxPower) 
                                << "\t" << ptr2sch->now() << endl;
        }
    }
    
    /* Schedules deletion of current mobile user */
    timestamp arrivalTime = ptr2sch->now();
    double deletionTime = randGen->exponential(1/ptr2param->callDuration);
    ptr2sch->schedule(Event(arrivalTime+timestamp(deletionTime),(void*)(&wrapperToDeleteUser),(void*)this,(long_integer)mobileUser->id));
    
    /* Schedules update of current mobile user's position */
    if (mobileUser->speed != 0) {
        int secCounter = 1;
        for (secCounter = 1; secCounter <= (int)deletionTime; secCounter++) {
            ptr2sch->schedule(Event(arrivalTime+timestamp((double)secCounter),(void*)(&wrapperToMoveUser),(void*)this,(long_integer)mobileUser->id));
        }
    }
    
    /* Schedules next mobile user */
    usersPerSecond = (ptr2param->maxUserPerSec)*(1-cos((2*3.14*(double)ptr2sch->now()/86400)+ptr2param->peakTime));
    usersPerSecond = pow(usersPerSecond,1.6)+0.001;
    ptr2sch->schedule(Event(arrivalTime+timestamp(randGen->exponential(usersPerSecond)),(void*)(&wrapperToNewUser),(void*)this));
    numberOfCreatedUsers++;
    
    /* Resets ID of mobile users */
    if (numberOfCreatedUsers >= MAX_MU) {numberOfCreatedUsers = 0;}
    
    
}

/**
 * Deletes mobile user from list
 */
void Traffic::deleteMobileUser(long_integer mobileUserID) {
    list<MobileUser>::iterator mobileUserListIterator;
    
    for (mobileUserListIterator = mobileUserList.begin(); mobileUserListIterator != mobileUserList.end(); mobileUserListIterator++) {
        if (mobileUserListIterator->id == (int)mobileUserID) {
            ptr2chan->snrMatrix[mobileUserListIterator->id][mobileUserListIterator->whichBaseStation] = 0;
            ptr2chan->rxPowerMatrix[mobileUserListIterator->id][mobileUserListIterator->whichBaseStation] = 0;
            ptr2chan->linkMatrix[mobileUserListIterator->id][mobileUserListIterator->whichBaseStation] = 0;
            mobileUserList.erase(mobileUserListIterator);
            break;
        }
    }
    
    /* Prints to log file */
    if (ptr2param->logFlag) {
        *(ptr2log->traffic) << "Deleted MU " << mobileUserListIterator->id << endl << endl;
    }
}

/**
 * Updates mobile user's position
 */
void Traffic::moveMobileUser(long_integer mobileUserID) {
    list<MobileUser>::iterator mobileUserListIterator;
    
    for (mobileUserListIterator = mobileUserList.end(); mobileUserListIterator != mobileUserList.begin(); mobileUserListIterator--) {
        if (mobileUserListIterator->id == (int)mobileUserID) {
            /* Chooses direction accordingly to mobile user's type of movement */
            if (mobileUserListIterator->speed <= 1.2) { // Brownian movement
                mobileUserListIterator->direction = randGen->discrete_uniform(0,7);
            }
            if (mobileUserListIterator->speed > 1.2 && mobileUserListIterator->speed <= 6.0) { // Pedestrian movement
                mobileUserListIterator->direction = randGen->discrete_uniform(mobileUserListIterator->direction-2,mobileUserListIterator->direction+2);
                if (mobileUserListIterator->direction == -2) {mobileUserListIterator->direction = 6;}
                if (mobileUserListIterator->direction == -1) {mobileUserListIterator->direction = 7;}
                if (mobileUserListIterator->direction == 8) {mobileUserListIterator->direction = 0;}
                if (mobileUserListIterator->direction == 9) {mobileUserListIterator->direction = 1;}
            }
            // else, direction remains the same // Car movement
            
            /* East */
            if (mobileUserListIterator->direction == 0) {
                mobileUserListIterator->xPos += mobileUserListIterator->speed;
            }
            /* North-East */
            if (mobileUserListIterator->direction == 1) {
                mobileUserListIterator->xPos += sqrt(2)*mobileUserListIterator->speed;
                mobileUserListIterator->yPos += sqrt(2)*mobileUserListIterator->speed;
            }
            /* North */
            if (mobileUserListIterator->direction == 2) {
                mobileUserListIterator->yPos += mobileUserListIterator->speed;
            }
            /* North-West */
            if (mobileUserListIterator->direction == 3) {
                mobileUserListIterator->xPos -= sqrt(2)*mobileUserListIterator->speed;
                mobileUserListIterator->yPos += sqrt(2)*mobileUserListIterator->speed;
            }
            /* West */
            if (mobileUserListIterator->direction == 4) {
                mobileUserListIterator->xPos -= mobileUserListIterator->speed;
            }
            /* South-West */
            if (mobileUserListIterator->direction == 5) {
                mobileUserListIterator->xPos -= sqrt(2)*mobileUserListIterator->speed;
                mobileUserListIterator->yPos -= sqrt(2)*mobileUserListIterator->speed;
            }
            /* South */
            if (mobileUserListIterator->direction == 6) {
                mobileUserListIterator->yPos -= mobileUserListIterator->speed;
            }
            /* South-East */
            if (mobileUserListIterator->direction == 7) {
                mobileUserListIterator->xPos += sqrt(2)*mobileUserListIterator->speed;
                mobileUserListIterator->yPos -= sqrt(2)*mobileUserListIterator->speed;
            }
            if (mobileUserListIterator->xPos < ptr2grid->boundaryMatrix[0][0] || mobileUserListIterator->yPos < ptr2grid->boundaryMatrix[0][1] || mobileUserListIterator->xPos > ptr2grid->boundaryMatrix[1][0] || mobileUserListIterator->xPos > ptr2grid->boundaryMatrix[1][1]) {
                mobileUserListIterator->xPos = (ptr2grid->boundaryMatrix[0][0]+ptr2grid->boundaryMatrix[1][0])/2;
                mobileUserListIterator->yPos = (ptr2grid->boundaryMatrix[0][1]+ptr2grid->boundaryMatrix[1][1])/2;
            }
            
            /*  */
            mobileUserListIterator->rxPower = ptr2chan->link(ptr2grid->baseStationVector,&*mobileUserListIterator);
            mobileUserListIterator->throughput = ptr2chan->linkThroughput(ptr2grid->baseStationVector,mobileUserList,mobileUserListIterator->id);
            break;
        }
    }
    
    /* Prints to log file *
    if (ptr2param->logFlag) {
        *(ptr2log->traffic) << "Moved MU " << mobileUserListIterator->id << "\t" << mobileUserListIterator->xPos << "\t" << mobileUserListIterator->yPos << endl;
    }*/
}

/**
 * Chooses current hotspot accordingly to day hour and sets mobile user's site
 */
int Traffic::chooseHotspot(int dayHour, int currDay) {
    int whichHotspot;
    double prob;
    
    if (dayHour >= 6 && dayHour <= 18) {
        prob = randGen->uniform(0,1);
        if (prob >= 0.35)
            whichHotspot = 1;
        else
            whichHotspot = randGen->discrete_uniform(2,4);
    }
    if ((dayHour > 18 && dayHour <= 23) || (dayHour >= 0 && dayHour < 6)) {
        prob = randGen->uniform(0,1);
        if (prob <= 0.15)
            whichHotspot = 1;
        else
            whichHotspot = randGen->discrete_uniform(2,4);
    }
    if (currDay%3 == 0 && dayHour >= 16 && dayHour < 18) {
        prob = randGen->uniform(0,1);
        if (prob >= 0.5)
            whichHotspot = randGen->discrete_uniform(1,4);
        else
            whichHotspot = 0;
    }
    
    return whichHotspot;
}
